﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace NumSkull
{
    public enum BinCalculationMethod
    {
        Even = 0,
        SquareRoot, 
    }

    public class Histogram
    {
        private readonly List<HistogramBin> _bins;

        public Histogram() { }

        public Histogram(Bounds bounds, int count)
            : this(bounds, count, BinCalculationMethod.Even)
        { }

        public Histogram(Bounds bounds, int count, BinCalculationMethod calculationMethod)
        {
            if (bounds == null) throw new ArgumentNullException("bounds");
            if (count <= 0) throw new ArgumentOutOfRangeException("count");

            _bins = GenerateBins(bounds, count, calculationMethod);
            Bounds = bounds;
        }

        public Histogram(IEnumerable<double> values)
        {
            if (values == null) throw new ArgumentNullException("values");

            Bounds = new Bounds(values.Min(), values.Max());
            _bins = GenerateBins(values);
        }

        public IList<HistogramBin> Bins { get { return _bins;  } }
        public Bounds Bounds { get; private set; }

        public void Clear()
        {
            Bounds = Bounds.Empty;
            Bins.Clear();
        }

        public void Add(double value)
        {
            var bin = Find(value);
            bin.Add(value);
        }

        public void Add(IEnumerable<double> values)
        {
            values.ForEach(Add);
        }

        public HistogramBin Find(double value)
        {
            var found = _bins.Find(bin => bin.Bounds.IsInRange(value));
            return found;
        }

        public IEnumerable<int> GetBinTotals()
        {
            var totals = _bins.Select(bin => bin.Count);
            return totals.ToList();
        }

        private static List<HistogramBin> GenerateBins(Bounds bounds, int count, BinCalculationMethod calculationMethod)
        {
            switch (calculationMethod)
            {
                case BinCalculationMethod.SquareRoot:
                    return CalculateSquareRootBins(bounds, count);

                default:
                    return CalculateEvenBins(bounds, count);
            }
        }

        private static List<HistogramBin> CalculateEvenBins(Bounds bounds, int count)
        {
            var step = bounds.Range / count;
            var values = Utility.SteppedRange(bounds.Lower, count, step);
            var bins = GenerateBins(values);
            return bins;
        }

        private static List<HistogramBin> CalculateSquareRootBins(Bounds bounds, int count)
        {
            var step = Math.Floor(Math.Sqrt(count));
            var values = Utility.SteppedRange(bounds.Lower, count, step);
            var bins = GenerateBins(values);
            return bins;
        }

        private static List<HistogramBin> GenerateBins(IEnumerable<double> values)
        {
            var sorted = values.OrderBy(d => d);
            var bins = sorted.Zip(sorted.Skip(1), (f, s) => new HistogramBin(new Bounds(f, s))).ToList();
            return bins;
        }
    }
}
